#include "ServerRegionManager.hpp"

ServerRegionManager::ServerRegionManager():RegionManager(){
    entityManager = 0;
    ServerRegion registerRegion;
    registerObject(&registerRegion);
    
    Character registerEntity;
    registerObject(&registerEntity);

	Player registerPlayer;
	registerObject(&registerPlayer);

    //Initialize lua side RegionManager.
    init("Objects/s_regions.lua");
}

/**
 * Update state of regions in server.
 * @todo Rewrite collision checking algorithm to avoid
 * double checking entities that have already been checked
 * in the onexit stage. Combine the duplicate code for
 * players and all other entities into something better.
 */
void ServerRegionManager::update(unsigned int frameTime){
    
    for(unsigned short i = 0; i < size; i++){
		if(regions[i]){
            ServerRegion* region = (ServerRegion*)regions[i];

            if(region->destroy){
                destroyRegion(region);
                continue;
            }


            //If region has no script updating is pointless.
            if(!region->hasScript)
                continue;

            //Check if any entities have left the region.
            for(int c = 0; c < region->entities.size(); c++){
                Character* entity = (Character*)region->entities.vector.at(c);

                //Entity is marked for destruction, remove it.
                if(entity->destroy){
                    region->entities.vector.erase(region->entities.vector.begin() + c);
                    continue;
                }
                
                if(!CollisionHelper::collides(region, entity)){
                    onexit(region, entity);
                    region->entities.vector.erase(region->entities.vector.begin() + c);
                    c--;
                }
            }

			//Check if any players have left the region.
			for(int c = 0; c < region->players.size(); c++){
                Player* entity = (Player*)region->players.vector.at(c);
                if(!CollisionHelper::collides(region, entity)){
                    onexit(region, entity);
                    region->players.vector.erase(region->players.vector.begin() + c);
                    c--;
                }
            }

            runUpdate(region);

            //Check for any new entities in region.
            for(unsigned short c = 0; c < entityManager->size; c++){
                if(entityManager->entities[c] && !entityManager->entities[c]->destroy){
                    Entity* entity = entityManager->entities[c];
                    if(CollisionHelper::collides(region, entity)){
                        
                        bool found = false;
                        for(int i = 0; i < region->entities.size(); i++){
                            if(region->entities.at(i) == entity){
                                found = true;
                                break;
                            }
                        }
                        if(!found){
                            onentry(region, entity);
                            region->entities.vector.push_back(entity);
                        }
                    }
                }
            }

			//Check for any new players in region.
			for(unsigned short c = 0; c < playerManager->size; c++){
				if(playerManager->players[c]){
                    Entity* entity = playerManager->players[c];
                    if(CollisionHelper::collides(region, entity)){
                        
                        bool found = false;
                        for(int i = 0; i < region->players.size(); i++){
                            if(region->players.at(i) == entity){
                                found = true;
                                break;
                            }
                        }
                        if(!found){
                            onentry(region, entity);
                            region->players.vector.push_back(entity);
                        }
                    }
                }
            }

		}
	}
}

Region* ServerRegionManager::createRegion(const char* name){
    //Find an empty element and create new region.
    for(unsigned short i = 0; i < size; i++){
        if(regions[i] == 0){
            regions[i] = new ServerRegion();
			regions[i]->init(name, i);

			//Find regions inside new region.
			for(int r = 0; r < size; r++){
				if(regions[r] != 0 && r != i && !regions[r]->destroy){

					if(CollisionHelper::collides(regions[i], regions[r])){
							((ServerRegion*)regions[i])->regions.vector.push_back((ServerRegion*)regions[r]);
					}
				}
			}

            //Run region startup script.
            runStartup(regions[i], name);
            
            return regions[i];
        }
    }
    return 0;
}

bool ServerRegionManager::destroyRegion(Region* region){
    if(regions[region->index] == region){
        if(region->destroy == BaseObject::d_Clean){
            region->destroy = BaseObject::d_Destroy;
            ((ServerRegion*)region)->entities.vector.clear();
            ((ServerRegion*)region)->players.vector.clear();
        }else if(region->destroy == BaseObject::d_Destroy){
            runDestroy(region);
            //Mark for deletion on next update.
            region->destroy = BaseObject::d_Delete;
        }else if(region->destroy == BaseObject::d_Delete){
            uint16_t index = region->index;
            delete regions[index];
            regions[index] = 0;
        }
        return true;
	}
    return false;
}

/**
 * Find a region by name.
 * @param name Name of region.
 * @return Pointer to a ServerRegion with given name or 0.
 */
ServerRegion* ServerRegionManager::findRegion(const char* name){
    return (ServerRegion*)RegionManager::findRegion(name);
}

void ServerRegionManager::bind(lua_State* luaState){
	luabind::module(luaState)[
        luabind::class_<ServerRegionManager>("RegionManager")
			.def_readonly("size", &ServerRegionManager::size)
			.def("destroyRegion", &ServerRegionManager::destroyRegion)
			.def("findRegion", &ServerRegionManager::findRegion)
    ];
}

void ServerRegionManager::push(lua_State* luaState){
	luabind::object o = luabind::object(luaState, this);
    o.push(luaState);
}